namespace OX.Strongbind
{
    using OX.Strongbind.Dynamic;
    using OX.Strongbind.Remoting;
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// The proxy manager takes care of creating and managing proxies for the given binding scope.
    /// </summary>
    internal class ProxyManager : IBindingPairProvider, IDisposable
    {
        private DynamicProxyGenerator dynamicProxyGenerator;
        private RemotingProxyGenerator remotingProxyGenerator;
        private NavigationPathHolder navigationPathHolder;
        private CrossDomainProxyGenerator crossDomainGenerator;
        private AppDomain proxyDomain;
        private readonly Dictionary<Guid, object> objects;
        private readonly bool useSeparateAppDomain;
        private readonly bool allowProxiesInCurrentAppDomain;


        internal ProxyManager(bool useSeparateAppDomain, bool allowProxiesInCurrentAppDomain)
        {
            this.useSeparateAppDomain = useSeparateAppDomain;
            this.allowProxiesInCurrentAppDomain = allowProxiesInCurrentAppDomain;

            navigationPathHolder = new NavigationPathHolder();
            remotingProxyGenerator = new RemotingProxyGenerator();
            dynamicProxyGenerator = new DynamicProxyGenerator();
            objects = new Dictionary<Guid, object>();

            if (useSeparateAppDomain)
            {
                proxyDomain = AppDomain.CreateDomain("StrongbindProxies", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
                crossDomainGenerator = (CrossDomainProxyGenerator)proxyDomain.CreateInstanceAndUnwrap(typeof(CrossDomainProxyGenerator).Assembly.FullName, typeof(CrossDomainProxyGenerator).FullName);
            }                            
        }

        /// <summary>
        /// Creates a new proxy for the given instance.
        /// </summary>
        /// <typeparam name="T">The type of the instance to create a proxy for.</typeparam>
        /// <param name="instance">The instance to create a proxy for.</param>
        /// <returns>A new proxy for the given instance.</returns>
        public T CreateProxy<T>(T instance)
        {
            Guid identity = Guid.NewGuid();

            Type type = typeof(T);
            T proxy;

            if (typeof(MarshalByRefObject).IsAssignableFrom(type))
                proxy = remotingProxyGenerator.For<T>(identity, navigationPathHolder);
            else if (useSeparateAppDomain && type.IsInterface)
                proxy = crossDomainGenerator.For<T>(identity, navigationPathHolder);
            else if (allowProxiesInCurrentAppDomain)
                proxy = dynamicProxyGenerator.For<T>(identity, navigationPathHolder);
            else
            {
                throw new InvalidOperationException(string.Format("Cannot generate a proxy for {0}. The proxy is not an interface and does not inherit System.MarshalByRefObject, and the binding scope does not allow proxies in the current application domain.", type.FullName));
            }

            objects.Add(identity, instance);

            return (T)proxy;
        }

        public BindingPair GetBindingPair()
        {
            Guid identity = navigationPathHolder.GetIdentity();
            string path = navigationPathHolder.GetLastNavigationPath();
            if (string.IsNullOrEmpty(path))
                return null;
            else
                navigationPathHolder.ClearNavigationPath();
            return new BindingPair(objects[identity], path);
        }

        public void Dispose()
        {
            remotingProxyGenerator.Dispose();
            remotingProxyGenerator = null;
            dynamicProxyGenerator = null;
            crossDomainGenerator = null;

            if (proxyDomain != null)
            {
                AppDomain.Unload(proxyDomain);
                proxyDomain = null;
            }
        }
    }
}
